<?php
/**
 * Class rail_sequence
 *
 * A rail_sequence is a stack of elements and implements rail_stack. Each element in sequence is a rail_object.
 * Its posible to get the sequence as rail_SequenceIterator object.
 *
 **
 * rail_object
 *   |
 *   + rail_sequence
 *
 * PHP versions 5
 * See http://www.open-rail.org for more informations.
 *
 **
 * Copyright (C)
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of ERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software
 * Fundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 **
 * @author Bastian Che Schröder <bastian@open-rail.org>
 * @version 2009-09-11
 * @package rail
 */
class rail_sequence extends rail_object implements rail_stack {

	/**
	 * Remove all elements from this sequence.
	 *
	 * @exception rail_writepermission_exception
	 */
	public function clear () {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		// remove all elements by MySQL request
		$this->rail_connector->send_request( "DELETE FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "'" );
	}


	/**
	 * Return a copy of this object.
	 *
	 * @exception rail_readpermission_exception
	 * @param rail_object $pEnvironment The new environment
	 * @param bool $pRecursive (optional)
	 * @return rail_object
	 */
	public function copy ( $pEnvironment, $pRecursive = false ) {
		$copy = parent::copy( $pEnvironment, false );

		// copy sequence
		$copy->set_sequence( $this->get_sequence() );

		// return copyed object
		return $copy;
	}
	
	
	/**
	 * Create a new rail_sequence. If requested push some rail_object Objects to created rail_sequence.
	 *
	 * @param rail_connector $pRailConnector
	 * @param rail_object $pEnvironment
	 * @param string $pName OBJ_NAME (optional)
	 * @param mixed $pRailObjects a rail_object or a array of rail_object Objects (optional).
	 * @return rail_sequence
	 */
	public static function create ( $pRailConnector, $pEnvironment, $pName = 'new sequence', $pRailObjects = null ) {
		$object = parent::create( $pRailConnector, $pEnvironment, $pName );
		$pRailConnector->send_request( "UPDATE " . $pRailConnector->mysql_table_prefix . "objects SET `type`='RAIL_SEQUENCE' WHERE `id`=" . $object->get_id() );

		if( $pRailObjects != null )
			$object->push( $pRailObjects );

		return $object;
	}


	/**
	 * Delete this rail_sequence. Clear sequence before deleting.
	 *
	 * @exception rail_writepermission_exception
	 */
	public function delete () {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		// remove content
		$this->clear();

		return parent::delete();
	}


	/**
	 * Return a new rail_sequenceIterator object. Optional you can define the start index for the
	 * rail_sequenceIterator object.
	 *
	 * @exception rail_exception|rail_readpermission_exception
	 * @param int $pIndex (optional)
	 * @return rail_sequenceIterator
	 */
	public function get_iterator ( $pIndex ) {
		// check read permission
		if( !$this->is_readable() )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );
		// check whether pIndex is a part of this sequence
		if( $pIndex != null ){
			$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `id`='$pIndex' LIMIT 1" );
			$element = $data->fetch_assoc();
			if( count( $element ) <= 0 )
				throw new rail_exception( $this, "The index $pIndex is not a part of this sequence.");
		}

		return new rail_sequenceIterator( $this, $pIndex );
	}


	/**
	 * Get all elements from sequence as array. The array keys are the index for the element.
	 *
	 * @exception rail_readpermission_exception
	 * @return array
	 */
	public function get_sequence () {
		// check read permission
		if( !$this->is_readable() )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		// get all elements
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "'" );

		// build array
		$objects= array();
		while( $data_row = $data->fetch_assoc() )
			$objects[ $data_row[ 'id' ] ] = rail_factory::get_object( $this->rail_connector, $data_row[ 'object' ] );

		return $objects;
	}


	/**
	 * Insert a new rail_object as element to this sequence. The optional parameter pIndex specified a index in this
	 * sequence, the new element will be stored direct before the index.
	 * If the parameter $pIndex isn't set or null the new element will be placed at the beginning of this sequence.
	 *
	 * @exception rail_writepermission_exception
	 * @param rail_object $pRailObject
	 * @param int $pIndex (optional)
	 */
	public function insert_element ( $pRailObject, $pIndex = null ) {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );
		// check object type of pRailObject
		if( !$pRailObject instanceof rail_object )
		 	throw new rail_param_exception( null, '$pRailObject', get_class($pRailObject) );
		// check whether pIndex is a part of this sequence
		if( $pIndex != null ){
			$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `id`='$pIndex' LIMIT 1" );
			$element = $data->fetch_assoc();
			if( count( $element ) <= 0 )
				throw new rail_exception( $this, "The index $pIndex is not a part of this sequence.");
		}

	 	// get next index for the new element
		$index = $pIndex != null ? $pIndex : 0;
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `previous`='$index' LIMIT 1" );
		// fetch element
		$element = $data->fetch_assoc();
		$next = count( $element ) > 0 ? $element[ 'next' ] : 0;

		// get previous index for the new element
		$previous = count( $element ) > 0 ? $element[ 'id' ] : 0;

		// insert new element into sequence
		$id = $this->rail_connector->send_request( "
			INSERT INTO
				" . $this->rail_connector->mysql_table_prefix . "objects_sequences (`sequence`,`object`,`previous`,`next`)
			VALUES
				('" . $this->get_id() . "','" . $pRailObject->get_id() . "','$previous','$next')
		" );

		// update previous element in sequence
		if( $previous != 0 )
			$this->rail_connector->send_request( "UPDATE " . $this->rail_connector->mysql_table_prefix . "objects_sequences SET `next`='$id' WHERE `id`='$previous' LIMIT 1" );

		// update next element in sequence
		if( $next != 0 )
			$this->rail_connector->send_request( "UPDATE " . $this->rail_connector->mysql_table_prefix . "objects_sequences SET `previous`='$id' WHERE `id`='$next' LIMIT 1" );
	}


	/**
	 * Returns true if this sequence is empty otherwise false.
	 *
	 * @exception rail_readpermission_exception
	 * @return boolean
	 */
	public function is_empty(){
		// check read permission
		if( !$this->is_readable() )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		// get sequence data
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' LIMIT 1" );
		$data = $data->fetch_assoc();
		return count( $data ) > 0;
	}


	/**
	 * Remove the element in top of this sequence. Return the removed rail_object.
	 *
	 * @exception rail_writepermission_exception
	 * @return rail_object
	 */
	public function pop () {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		 // get element
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `next`='0' LIMIT 1" );
		// fetch element
		$element = $data->fetch_assoc();
		if( count( $element ) <= 0 )
			throw new rail_exception( $this, 'The sequence is empty.' );

		$this->remove_element( $element[ 'id' ] );
		return rail_factory::get_object( $this->rail_connector, $element[ 'object' ] );
	}


	/**
	 * Add a new element onto the top of this sequence.
	 *
	 * @exception rail_writepermission_exception
	 * @param array|rail_object $pRailObjects
	 */
	public function push ( $pRailObjects ) {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$objects = is_array($pRailObjects) ? $pRailObjects : array( $pRailObjects );

		// get previous index
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `next`='0' LIMIT 1" );
		$element = $data->fetch_assoc();
		$index = count( $element ) > 0 ? $element[ 'id' ] : null;

		// insert all objects
		foreach( $objects as $object )
			$index = $this->insert_element( $object, $index );
	}


	/**
	 * Remove one element from this sequence. Require the index for the element how should be deleted.
	 *
	 * @param int $pId
	 */
	public function remove_element ( $pIndex ) {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );
		// check whether pIndex is a part of this sequence
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `id`='$pIndex' LIMIT 1" );
		$element = $data->fetch_assoc();
		if( count( $element ) <= 0 )
			throw new rail_exception( $this, "The index $pIndex is not a part of this sequence.");

	 	// get element by index
		$data = $this->rail_connector->send_request( "SELECT * FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `sequence`='" . $this->get_id() . "' AND `previous`='$pIndex' LIMIT 1" );
		// fetch element
		$element = $data->fetch_assoc();

		// get previous index for the new element
		$previous = count( $element ) > 0 ? $element[ 'id' ] : 0;

		// insert new element into sequence
		$id = $this->rail_connector->send_request( "
			INSERT INTO
				" . $this->rail_connector->mysql_table_prefix . "objects_sequences (`sequence`,`object`,`previous`,`next`)
			VALUES
				('" . $this->get_id() . "','" . $pRailObject->get_id() . "','$previous','$next')
		" );

		// update previous element in sequence
		if( $element[ 'previous' ] != 0 )
			$this->rail_connector->send_request( "UPDATE " . $this->rail_connector->mysql_table_prefix . "objects_sequences SET `next`='" . $element[ 'next' ] . "' WHERE `id`='" . $element[ 'previous' ] . "' LIMIT 1" );

		// update next element in sequence
		if( $element[ 'next' ] != 0 )
			$this->rail_connector->send_request( "UPDATE " . $this->rail_connector->mysql_table_prefix . "objects_sequences SET `previous`='" . $element[ 'previous' ] . "' WHERE `id`='" . $element[ 'next' ] . "' LIMIT 1" );

		// delete record
		$this->rail_connector->send_request( "DELETE FROM " . $this->rail_connector->mysql_table_prefix . "objects_sequences WHERE `id`='$pIndex'" );
	}


	/**
	 * Set an array of rail_objects as this sequence. Clear the sequence before adding new elements.
	 *
	 * @exception rail_writepermission_exception
	 * @param array $pRailObjects
	 */
	public function set_sequence ( $pRailObjects ) {
		// check write permission
		if( !$this->is_writeable() )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$this->clear();

		// insert all objects
		$index = null;
		foreach( $objects as $object )
			$index = $this->insert_element( $object, $index );
	}

} // End of rail_sequence
?>